/*
    generate arm instructions
    Copyright (C) 2011  Jiabo <jiabo2011@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "../ioport.h"

#include "arm-instr.h"
#include "gen_div.h"

static inline void mark_b_label(uint8_t label)
{
	gen_b_label_buf[label * 2] = gen_code_ptr;
}

static inline void gen_b_label_cond(uint8_t cond, uint8_t label)
{
	gen_b_label_buf[label * 2 + 1] = gen_code_ptr;
	gen_b_label_count++;
	*gen_code_ptr++ = 0x0a000000 | cond << 28;
}

static inline void gen_b_label(uint8_t label)
{
	gen_b_label_cond(COND_AL, label);
}

static int in_8bit_map(uint32_t imm32, uint8_t *imm8, uint8_t *rorate_imm)
{
	int i;
	uint32_t tmp;

	for (i = 0; i < 16; i++) {
		if ((imm32 & 0xffffff00) == 0) {
			*rorate_imm = i;
			*imm8 = (uint8_t)imm32;
			return 1;
		} else {
			tmp = imm32;
			imm32 <<= 2;
			imm32 |= (tmp >> 30) & 0x3; 
		}
	}
	return 0;
}

#define load_x86reg(ot, arm_reg, x86_reg) \
	arm_ldr_reg_env(ot, arm_reg, offset_reg(ot, x86_reg));
#define store_x86reg(ot, arm_reg, x86_reg) \
	arm_str_env_reg(ot, arm_reg, offset_reg(ot, x86_reg));
#define load_x86sreg(arm_reg, x86_sreg) \
	arm_ldr_reg_env(OT_LONG, arm_reg, offset_sreg(x86_sreg));
#define store_x86sreg(arm_reg, x86_sreg) \
	arm_str_env_reg(OT_LONG, arm_reg, offset_sreg(x86_sreg));
#define load_x86cr(arm_reg, x86_cr) \
	arm_ldr_reg_env(OT_LONG, arm_reg, offset_cr(x86_cr));
#define store_x86cr(arm_reg, x86_cr) \
	arm_str_env_reg(OT_LONG, arm_reg, offset_cr(x86_cr));
#define load_x86env(ot, arm_reg, member) \
	arm_ldr_reg_env(ot, arm_reg, offset(member));
#define store_x86env(ot, arm_reg, member) \
	arm_str_env_reg(ot, arm_reg, offset(member));

#if 0
static inline void gen_add_reg_imm_cond(uint8_t cond,
	uint8_t reg, uint32_t imm32, uint8_t scratch)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_add_reg_imm_cond(cond, reg, immed_8, rorate_imm);
	} else if (in_8bit_map(~imm32, &immed_8, &rorate_imm)) {
		arm_sub_reg_imm_cond(cond, reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm_cond(cond, scratch, imm32);
		arm_add_reg_cond(cond, reg, reg, scratch);
	}
}
#endif

static inline void gen_add_reg_imm(uint8_t reg, uint32_t imm32, uint8_t scratch)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_add_reg_imm(reg, immed_8, rorate_imm);
	} else if (in_8bit_map(~imm32, &immed_8, &rorate_imm)) {
		arm_sub_reg_imm(reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm(scratch, imm32);
		arm_add_reg(reg, reg, scratch);
	}
}

#if 0
static inline void gen_sub_reg_imm_cond(uint8_t cond,
	uint8_t reg, uint32_t imm32, uint8_t scratch)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_sub_reg_imm_cond(cond, reg, immed_8, rorate_imm);
	} else if (in_8bit_map(~imm32, &immed_8, &rorate_imm)) {
		arm_add_reg_imm_cond(cond, reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm_cond(cond, scratch, imm32);
		arm_sub_reg_cond(cond, reg, reg, scratch);
	}
}
#endif

static inline void gen_sub_reg_imm(uint8_t reg, uint32_t imm32, uint8_t scratch)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_sub_reg_imm(reg, immed_8, rorate_imm);
	} else if (in_8bit_map(~imm32, &immed_8, &rorate_imm)) {
		arm_add_reg_imm(reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm(scratch, imm32);
		arm_sub_reg(reg, reg, scratch);
	}
}

static inline void gen_and_reg_imm(uint8_t reg, uint32_t imm32, uint8_t scratch)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_and_reg_imm(reg, immed_8, rorate_imm);
	} else if (in_8bit_map(~imm32, &immed_8, &rorate_imm)) {
		arm_bic_reg_imm(reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm(scratch, imm32);
		arm_and_reg(reg, reg, scratch);
	}
}

static inline void gen_orr_reg_imm(uint8_t reg, uint32_t imm32, uint8_t scratch)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_orr_reg_imm(reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm(scratch, imm32);
		arm_orr_reg(reg, reg, scratch);
	}
}

static inline void gen_eor_reg_imm(uint8_t reg, uint32_t imm32, uint8_t scratch)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_eor_reg_imm(reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm(scratch, imm32);
		arm_eor_reg(reg, reg, scratch);
	}
}

static inline void gen_mov_reg_imm(uint8_t reg, uint32_t imm32)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_mov_reg_imm(reg, immed_8, rorate_imm);
	} else if (in_8bit_map(~imm32, &immed_8, &rorate_imm)) {
		arm_mvn_reg_imm(reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm(reg, imm32);
	}
}

static inline void gen_mov_reg_imm_cond(uint8_t cond, uint8_t reg, uint32_t imm32)
{
	uint8_t immed_8, rorate_imm;

	if (in_8bit_map(imm32, &immed_8, &rorate_imm)) {
		arm_mov_reg_imm_cond(cond, reg, immed_8, rorate_imm);
	} else if (in_8bit_map(~imm32, &immed_8, &rorate_imm)) {
		arm_mvn_reg_imm_cond(cond, reg, immed_8, rorate_imm);
	} else {
		arm_ldr_reg_imm_cond(cond, reg, imm32);
	}
}

static inline void gen_data(void)
{
	uint32_t *tmp_ptr = gen_ldr_str_buf;
	uint32_t *instr;
	int offset;

	while (tmp_ptr < gen_ldr_str_ptr) {
		instr = (uint32_t *)*tmp_ptr++;
		offset = (uint32_t)gen_code_ptr - (uint32_t)instr - 8;
		*instr |= (offset & 0xfff);
		if (offset < 0)
			*instr &= ~(1 << 23);
		*gen_code_ptr++ = *tmp_ptr++;
	}
	gen_ldr_str_ptr = gen_ldr_str_buf;
{
	int i;

	for (i = 0; gen_b_label_count; i++) {
		instr = gen_b_label_buf[i * 2 + 1];
		offset = gen_b_label_buf[i * 2] - gen_b_label_buf[i * 2 + 1] - 2;
		*instr |= offset & 0xffffff;
		gen_b_label_count--;
	}
}
}

static inline void gen_exit_tb(void)
{
	arm_mov_reg(arm_pc, arm_lr);
	gen_data();
}

static inline void gen_add_reg_SREG(uint8_t rd, uint8_t rs, uint32_t sreg, uint8_t scratch)
{
	load_x86sreg(scratch, sreg);
	arm_add_reg_lsl_imm(rd, rs, scratch, 4);
}

static inline void gen_mov_REG_REG(int ot, uint32_t rd, uint32_t rn)
{
	if (rd != rn) {
		load_x86reg(ot, T0, rn);
		store_x86reg(ot, T0, rd);
	}
}

static inline void gen_mov_REG_CR(uint32_t reg, uint32_t cr)
{
	load_x86cr(T0, cr);
	store_x86reg(OT_LONG, T0, reg);
}

static inline void gen_mov_CR_REG(uint32_t cr, uint32_t reg)
{
	load_x86reg(OT_LONG, T0, reg);
	store_x86cr(T0, cr);
}

#include "gen_flags.h"
#include "gen_arith.h"

static inline void gen_shifti(DisasContext *s1, int op, int ot, uint32_t reg, uint8_t shift)
{
	if (shift == 0)
		return;

	shift &= 0x1f;
	switch(op) {
	case OP_SHL:
		load_x86reg(ot, OP1, reg);
		arm_mov_reg_lsl_imm(T1, OP1, shift);
		store_x86reg(ot, T1, reg);
		gen_mov_reg_imm(OP2, shift);
		s1->cc_op = CC_OP_SHLB + ot;
		s1->opc_valid = 1;
		break;
	case OP_SHR:
		load_x86reg(ot, OP1, reg);
		arm_mov_reg_lsr_imm(T1, OP1, shift);
		store_x86reg(ot, T1, reg);
		gen_mov_reg_imm(OP2, shift);
		s1->cc_op = CC_OP_SHRB + ot;
		s1->opc_valid = 1;
		break;
	default:
		printf("unknown shift cmd\n");
		exit(0);
	}
}

/*
	IN: OP2 --- shift count
*/
static inline void gen_shift(DisasContext *s1, int op, int ot, uint32_t reg, uint8_t shift_reg)
{
	switch(op) {
	case OP_SHL:
		load_x86reg(ot, OP1, reg);
		arm_mov_reg_lsl_reg(T1, OP1, shift_reg);
		store_x86reg(ot, T1, reg);
		s1->cc_op = CC_OP_SHLB + ot;
		s1->opc_valid = 1;
		break;
	case OP_SHR:
		load_x86reg(ot, OP1, reg);
		arm_mov_reg_lsr_reg(T1, OP1, shift_reg);
		store_x86reg(ot, T1, reg);
		s1->cc_op = CC_OP_SHRB + ot;
		s1->opc_valid = 1;
		break;
	default:
		printf("unknown shift cmd\n");
		exit(0);
	}
}

static inline void gen_inc_REG(DisasContext *s1, int ot, uint32_t reg)
{
//	gen_str_CF(s1);
	load_x86reg(ot, OP1, reg);
	arm_add_rd_rn_imm(T1, OP1, 1, 0);
	store_x86reg(ot, T1, reg);
	s1->cc_op = CC_OP_INCB + ot;
	s1->opc_valid = 1;
}

static inline void gen_dec_REG(DisasContext *s1, int ot, uint32_t reg)
{
//	gen_str_CF(s1);
	gen_x86_flags(s1);
	load_x86reg(ot, OP1, reg);
	arm_sub_rd_rn_imm(T1, OP1, 1, 0);
	store_x86reg(ot, T1, reg);
	s1->cc_op = CC_OP_DECB + ot;
	s1->opc_valid = 1;
}

static inline void gen_push_imm(DisasContext *s1, int ot, uint32_t imm32)
{
	if (s1->ss32) {
		load_x86reg(ot, T0, R_ESP);
		arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
		store_x86reg(ot, T0, R_ESP);
	} else {
		load_x86reg(OT_WORD, T0, R_ESP);
		arm_sub_reg_imm(T0, (ot == OT_WORD)? 2 : 4, 0);
		store_x86reg(OT_WORD, T0, R_ESP);
		gen_add_reg_SREG(T0, T0, R_SS, T1);
	}

	arm_add_reg(T0, T0, MI);
	gen_mov_reg_imm(T1, imm32);
	arm_str_data(ot, T0, T1);
}

static inline void gen_jmp_cond(uint8_t cond, uint32_t eip)
{
	gen_mov_reg_imm_cond(cond, SPC, eip);
}

static inline void gen_jmp(uint32_t eip)
{
	gen_jmp_cond(COND_AL, eip);
}

//extern void ljmp_protected(CPUX86State *env, uint32_t new_cs, uint32_t new_eip);
static inline void gen_ljmp(DisasContext *s1, CPUX86State *cpu,
	uint16_t selector, uint32_t offset)
{
	if (s1->pe) {
#if 0
		uint32_t func = (uint32_t)ljmp_protected;
	
		gen_push_lr();
		gen_mov_reg_imm(SPC, offset);
		gen_mov_reg_imm(T0, (uint32_t)cpu);
		gen_mov_reg_imm(T1, selector);
		gen_mov_reg(T2, SPC);
		/* ldr */
		gen_mov_reg(arm_lr, arm_pc);
		gen_mov_reg_imm(arm_pc, func);
		gen_pop_pc();
#endif
	} else {
		gen_mov_reg_imm(T0, selector);
		store_x86sreg(T0, R_CS);
		gen_mov_reg_imm(SPC, offset);
	}
}

static inline void gen_cld(void)
{
	gen_mov_reg_imm(T0, 0);
	store_x86env(OT_LONG, T0, df);
}

static inline void gen_std(void)
{
	gen_mov_reg_imm(T0, 1);
	store_x86env(OT_LONG, T0, df);
}

static inline void gen_ldt(DisasContext *s1, int ot, int op)
{
	/* T0 saves the GDT/IDT address */
	arm_add_reg(T0, T0, MI);
	arm_pop_addr_reg(OT_WORD, T0, T1);
	if (op == 2) {
		store_x86env(OT_LONG, T1, gdt.limit);
	} else {
		store_x86env(OT_LONG, T1, idt.limit);
	}
	arm_ldr_data(OT_LONG, T0, T1);
	if (!ot)
		arm_bic_reg_imm(T1, 0xff, 4);
	if (op == 2) {
		store_x86env(OT_LONG, T1, gdt.base);
	} else {
		store_x86env(OT_LONG, T1, idt.base);
	}
}

static inline void gen_movs(DisasContext *s, int ot)
{
	uint32_t data_offset = 0;

	if (s->aflag) {
		load_x86reg(OT_LONG, T1, R_ESI);
		load_x86reg(OT_LONG, T2, R_EDI);
	} else {
		load_x86reg(OT_WORD, T1, R_ESI);
		load_x86reg(OT_WORD, T2, R_EDI);
	}
	gen_add_reg_SREG(T1, T1, R_DS, T0);
	gen_add_reg_SREG(T2, T2, R_ES, T0);

	arm_add_reg(T1, T1, MI);
	arm_add_reg(T2, T2, MI);

	load_x86env(OT_LONG, T0, df);
	arm_cmp_reg_imm(T0, 0, 0);

	arm_ldr_data(ot, T1, T0);
	arm_push_reg_dir_cond(COND_EQ, ot, T2, T0, 1);
	arm_push_reg_dir_cond(COND_NE, ot, T2, T0, 0);
	data_offset += 1 << ot;

	if (s->aflag) {
		load_x86reg(OT_LONG, T1, R_ESI);
		load_x86reg(OT_LONG, T2, R_EDI);
		arm_add_reg_imm_cond(COND_EQ, T1, data_offset, 0);
		arm_sub_reg_imm_cond(COND_NE, T1, data_offset, 0);
		arm_add_reg_imm_cond(COND_EQ, T2, data_offset, 0);
		arm_sub_reg_imm_cond(COND_NE, T2, data_offset, 0);
		store_x86reg(OT_LONG, T1, R_ESI);
		store_x86reg(OT_LONG, T2, R_EDI);
	} else {
		load_x86reg(OT_WORD, T1, R_ESI);
		load_x86reg(OT_WORD, T2, R_EDI);
		arm_add_reg_imm_cond(COND_EQ, T1, data_offset, 0);
		arm_sub_reg_imm_cond(COND_NE, T1, data_offset, 0);
		arm_add_reg_imm_cond(COND_EQ, T2, data_offset, 0);
		arm_sub_reg_imm_cond(COND_NE, T2, data_offset, 0);
		store_x86reg(OT_WORD, T1, R_ESI);
		store_x86reg(OT_WORD, T2, R_EDI);
	}
}

static inline void gen_stos(DisasContext *s, int ot)
{
	uint32_t data_offset = 0;

	if (s->aflag) {
		load_x86reg(OT_LONG, T2, R_EDI);
	} else {
		load_x86reg(OT_WORD, T2, R_EDI);
	}
	gen_add_reg_SREG(T0, T2, R_ES, T1);
	arm_add_reg(T0, T0, MI);

	load_x86env(OT_LONG, T1, df);
	arm_cmp_reg_imm(T1, 0, 0);

	load_x86reg(ot, T1, R_EAX);
	arm_push_reg_dir_cond(COND_EQ, ot, T0, T1, 1);
	arm_push_reg_dir_cond(COND_NE, ot, T0, T1, 0);
	data_offset += 1 << ot;

	arm_add_reg_imm_cond(COND_EQ, T2, data_offset, 0);
	arm_sub_reg_imm_cond(COND_NE, T2, data_offset, 0);
	if (s->aflag) {
		store_x86reg(OT_LONG, T2, R_EDI);
	} else {
		store_x86reg(OT_WORD, T2, R_EDI);
	}
}

static inline void gen_scas(DisasContext *s, int ot)
{
	uint32_t data_offset = 0;

	if (s->aflag) {
		load_x86reg(OT_LONG, T2, R_EDI);
	} else {
		load_x86reg(OT_WORD, T2, R_EDI);
	}
	gen_add_reg_SREG(T0, T2, R_ES, T1);
	arm_add_reg(T0, T0, MI);

	load_x86env(OT_LONG, T1, df);
	arm_cmp_reg_imm(T1, 0, 0);

	load_x86reg(ot, OP1, R_EAX);
	arm_ldr_data(ot, T0, OP2);
	arm_sub_reg(T1, OP1, OP2);
	data_offset += 1 << ot;

	arm_add_reg_imm_cond(COND_EQ, T2, data_offset, 0);
	arm_sub_reg_imm_cond(COND_NE, T2, data_offset, 0);
	if (s->aflag) {
		store_x86reg(OT_LONG, T2, R_EDI);
	} else {
		store_x86reg(OT_WORD, T2, R_EDI);
	}
}

static inline void gen_ins(DisasContext *s, int ot)
{
	uint32_t data_offset = 0;
	uint32_t func = (uint32_t)ioport_read;

	if (s->aflag) {
		load_x86reg(OT_LONG, T2, R_EDI);
	} else {
		load_x86reg(OT_WORD, T2, R_EDI);
	}
	gen_add_reg_SREG(T2, T2, R_ES, T1);
	arm_add_reg(T2, T2, MI);

	arm_push_r2();
	arm_push_r3();
	arm_push_lr();
	gen_mov_reg_imm(T0, ot);
	load_x86reg(ot, T1, R_EDX)
	arm_mov_reg(arm_lr, arm_pc);
	gen_mov_reg_imm(arm_pc, func);
	arm_pop_lr();
	arm_pop_r3();
	arm_pop_r2();

	load_x86env(OT_LONG, T1, df);
	arm_cmp_reg_imm(T1, 0, 0);

	arm_push_reg_dir_cond(COND_EQ, ot, T2, T0, 1);
	arm_push_reg_dir_cond(COND_NE, ot, T2, T0, 0);
	data_offset += 1 << ot;

	if (s->aflag) {
		load_x86reg(OT_LONG, T2, R_EDI);
	} else {
		load_x86reg(OT_WORD, T2, R_EDI);
	}
	arm_add_reg_imm_cond(COND_EQ, T2, data_offset, 0);
	arm_sub_reg_imm_cond(COND_NE, T2, data_offset, 0);
	if (s->aflag) {
		store_x86reg(OT_LONG, T2, R_EDI);
	} else {
		store_x86reg(OT_WORD, T2, R_EDI);
	}
}

static inline void gen_outs(DisasContext *s, int ot)
{
	uint32_t data_offset = 0;
	uint32_t func = (uint32_t)ioport_write;

	if (s->aflag) {
		load_x86reg(OT_LONG, T1, R_ESI);
	} else {
		load_x86reg(OT_WORD, T1, R_ESI);
	}
	gen_add_reg_SREG(T1, T1, R_ES, T2);
	arm_add_reg(T1, T1, MI);

	arm_push_r3();
	arm_push_lr();
	arm_ldr_data(OT_LONG, T1, T2);
	gen_mov_reg_imm(T0, ot);
	load_x86reg(ot, T1, R_EDX)
	arm_mov_reg(arm_lr, arm_pc);
	gen_mov_reg_imm(arm_pc, func);
	arm_pop_lr();
	arm_pop_r3();

	load_x86env(OT_LONG, T1, df);
	arm_cmp_reg_imm(T1, 0, 0);
	data_offset += 1 << ot;

	if (s->aflag) {
		load_x86reg(OT_LONG, T2, R_ESI);
	} else {
		load_x86reg(OT_WORD, T2, R_ESI);
	}
	arm_add_reg_imm_cond(COND_EQ, T2, data_offset, 0);
	arm_sub_reg_imm_cond(COND_NE, T2, data_offset, 0);
	if (s->aflag) {
		store_x86reg(OT_LONG, T2, R_ESI);
	} else {
		store_x86reg(OT_WORD, T2, R_ESI);
	}
}

#define GEN_REP(op)\
static inline void gen_rep_ ## op(DisasContext *s, int ot, uint32_t next_eip)\
{\
	int a_ot = s->aflag ? OT_LONG : OT_WORD;\
\
	load_x86reg(a_ot, R0, R_ECX);\
\
	mark_b_label(0);\
	arm_cmp_reg_imm(R0, 0, 0);\
	gen_mov_reg_imm_cond(COND_EQ, SPC, next_eip);\
	gen_b_label_cond(COND_EQ, 1);\
\
	gen_ ## op(s, ot);\
	arm_sub_reg_imm(R0, 1, 0);\
	gen_b_label(0);\
\
	mark_b_label(1);\
	store_x86reg(a_ot, R0, R_ECX);\
}

GEN_REP(movs)
GEN_REP(stos)
GEN_REP(ins)
GEN_REP(outs)

#define GEN_REPNZ(op)\
static inline void gen_repnz_ ## op(DisasContext *s, int ot, uint32_t next_eip)\
{\
	int a_ot = s->aflag ? OT_LONG : OT_WORD;\
\
	load_x86reg(a_ot, R0, R_ECX);\
\
	mark_b_label(0);\
	arm_cmp_reg_imm(R0, 0, 0);\
	gen_mov_reg_imm_cond(COND_EQ, SPC, next_eip);\
	gen_b_label_cond(COND_EQ, 1);\
\
	gen_ ## op(s, ot);\
	arm_sub_reg_imm(R0, 1, 0);\
	arm_cmp_reg_imm(T1, 0, 0);\
	gen_b_label_cond(COND_NE, 0);\
\
	mark_b_label(1);\
	store_x86reg(a_ot, R0, R_ECX);\
}

GEN_REPNZ(scas)
